/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Reflection;
using System.Collections.Generic;
using Apache.NMS.ActiveMQ.Util;
using Apache.NMS.ActiveMQ.Transport.Discovery;
using Apache.NMS.ActiveMQ.Transport.Failover;
using Apache.NMS.ActiveMQ.Transport.Mock;
using Apache.NMS.ActiveMQ.Transport.Tcp;

namespace Apache.NMS.ActiveMQ.Transport
{
	public class TransportFactory
	{
		public static event ExceptionListener OnException;

        private static readonly FactoryFinder<ActiveMQTransportFactoryAttribute, ITransportFactory> FACTORY_FINDER =
            new FactoryFinder<ActiveMQTransportFactoryAttribute, ITransportFactory>();

        private static IDictionary<String, Type> TRANSPORT_FACTORY_TYPES = new Dictionary<String, Type>();

		public static void HandleException(Exception ex)
		{
			if(TransportFactory.OnException != null)
			{
				TransportFactory.OnException(ex);
			}
		}

        public void RegisterTransportFactory(string scheme, Type factoryType)
        {
            TRANSPORT_FACTORY_TYPES[scheme] = factoryType;
        }

		/// <summary>
		/// Creates a normal transport. 
		/// </summary>
		/// <param name="location"></param>
		/// <returns>the transport</returns>
		public static ITransport CreateTransport(Uri location)
		{
			ITransportFactory tf = TransportFactory.CreateTransportFactory(location);
			return tf.CreateTransport(location);
		}

		public static ITransport CompositeConnect(Uri location)
		{
			ITransportFactory tf = TransportFactory.CreateTransportFactory(location);
			return tf.CompositeConnect(location);
		}

		public static ITransport AsyncCompositeConnect(Uri location, SetTransport setTransport)
		{
			ITransportFactory tf = TransportFactory.CreateTransportFactory(location);
			return tf.CompositeConnect(location, setTransport);
		}

		/// <summary>
		/// Create a transport factory for the scheme.  If we do not support the transport protocol,
		/// an NMSConnectionException will be thrown.
		/// </summary>
		/// <param name="location"></param>
		/// <returns></returns>
		private static ITransportFactory CreateTransportFactory(Uri location)
		{
			string scheme = location.Scheme;

			if(string.IsNullOrEmpty(scheme))
			{
				throw new NMSConnectionException(String.Format("Transport scheme invalid: [{0}]", location.ToString()));
			}

			ITransportFactory factory = null;

			try
			{
                factory = NewInstance(scheme.ToLower());
			}
			catch(NMSConnectionException)
			{
				throw;
			}
			catch(Exception e)
			{
				throw new NMSConnectionException("Error creating transport.", e);
			}

			if(null == factory)
			{
				throw new NMSConnectionException("Unable to create a transport.");
			}

			return factory;
		}

        private static ITransportFactory NewInstance(string scheme)
        {
            try
            {
                Type factoryType = FindTransportFactory(scheme);

                if(factoryType == null)
                {
                    throw new Exception("NewInstance failed to find a match for id = " + scheme);
                }

                return (ITransportFactory) Activator.CreateInstance(factoryType);
            }
            catch(Exception ex)
            {
                Tracer.WarnFormat("NewInstance failed to create an ITransportFactory with error: {1}", ex.Message);
                throw;
            }
        }

        private static Type FindTransportFactory(string scheme)
        {
            if(TRANSPORT_FACTORY_TYPES.ContainsKey(scheme))
            {
                return TRANSPORT_FACTORY_TYPES[scheme];
            }

            try
            {
                Type factoryType = FACTORY_FINDER.FindFactoryType(scheme);
                TRANSPORT_FACTORY_TYPES[scheme] = factoryType;
                return factoryType;
            }
            catch
            {
                throw new NMSConnectionException("Failed to find Factory for Transport type: " + scheme);
            }
        }
	}
}
